/*==============================================================================
            Copyright (c) 2013 Moritz Hader, Marcel Schrder
            All Rights Reserved.

	@file  HeimdallNative3DObject.h

	@brief This header provides the class-definitions of the HeimdallNative3DObject.
	The 3DObject capsulates the mesh and surface-data received from the java-instance
	of this program and provides functionality to create the corresponding
	OpenGL-information (like matrix-data)
   
   This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
================================================================================*/


#include <engine/HeimdallNative3DObject.h>

#include <egl/HeimdallGLUtilities.h>
#include <engine/HeimdallNativeMesh.h>
#include <engine/HeimdallNativeSurface.h>
#include <egl/ShaderManagementUnit.h>
#include <egl/MatrixManagementUnit.h>


HeimdallNative3DObject::HeimdallNative3DObject(){
	isFinalized = false;
	markerID = 0;
}

HeimdallNative3DObject::~HeimdallNative3DObject(){
	if(this->nativeMesh != NULL)
	{
		delete this->nativeMesh;
	}

	if(this->nativeSurface != NULL)
	{
		delete this->nativeSurface;
	}
}

/*void HeimdallNative3DObject::loadJavaObject(JNIEnv* env, jobject nativeObj, int index){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Loading Heimdall3DObject into its native counterpart.");

	//Handle to the Heimdall3DObject-Class
	jclass hdClass = env->GetObjectClass(nativeObj);

		if (hdClass == NULL){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the Heimdall3dObject java-class into the ndk-environment.");
			return;
		}

	//Get the modifier-handle to set the identifier-index
	jmethodID setIdentifier = env->GetMethodID(hdClass, "setIdentifier", "(I)V");


		if(setIdentifier == 0){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, method setIdentifier not found");
			return;
		}

		env->CallObjectMethod(nativeObj, setIdentifier, index);

	//Get the method-handle to load the HeimdallMesh-object
	jmethodID getMesh = env->GetMethodID(hdClass, "getMesh", "()Lde/ibm/dhbw/heimdall/engine/HeimdallMesh;");

		if(getMesh == 0){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, method getMesh not found");
			return;
		}

		jobject meshObj = env->CallObjectMethod(nativeObj, getMesh);

		//Propagate mesh-object

		this->nativeMesh = new HeimdallNativeMesh();
		this->nativeMesh->loadJavaObject(env, meshObj);


	//Get the method-handle to load the HeimdallMesh-object
	jmethodID getSurface = env->GetMethodID(hdClass, "getSurface", "()Lde/ibm/dhbw/heimdall/engine/HeimdallSurface;");

		if(getSurface == 0){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, method getSurface not found");
			return;
		}

		jobject surfaceObj = env->CallObjectMethod(nativeObj, getSurface);

		//Propagate surface-object
		this->nativeSurface = new HeimdallNativeSurface();
		this->nativeSurface->loadJavaObject(env, surfaceObj);

	//Load the properties for location, rotation and scale

		//loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedPositionFloat", &this->nativePosition);

		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "MSGP: [0]: %f [1]: %f [2]: %f", this->nativePosition[0], this->nativePosition[1], this->nativePosition[2]);
		updatePosition(env, nativeObj, hdClass);

		//loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedRotationFloat", &this->nativeRotation);

		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "MSGR: [0]: %f [1]: %f [2]: %f", this->nativeRotation[0], this->nativeRotation[1], this->nativeRotation[2]);
		updateRotation(env, nativeObj, hdClass);


		//loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedScaleFloat", &this->nativeScale);

		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "MSGS: [0]: %f [1]: %f [2]: %f", this->nativeScale[0], this->nativeScale[1], this->nativeScale[2]);
		updateScale(env, nativeObj, hdClass);

}*/

void HeimdallNative3DObject::loadJavaObject(JNIEnv* env, jobject nativeObj){

	__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Loading Heimdall3DObject into its native counterpart.");

	//Handle to the Heimdall3DObject-Class
	jclass hdClass = env->GetObjectClass(nativeObj);

		if (hdClass == NULL){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, could not load the Heimdall3dObject java-class into the ndk-environment.");
			return;
		}

	//Get the modifier-handle to set the identifier-index
	jmethodID setIdentifier = env->GetMethodID(hdClass, "setIdentifier", "(I)V");


		if(setIdentifier == 0){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, method setIdentifier not found");
			return;
		}

		env->CallObjectMethod(nativeObj, setIdentifier, this->getIndex());

	//Get the marker-identifier
	markerID = getCorrespondingMarker(env, nativeObj, hdClass);

	//Get the method-handle to load the HeimdallMesh-object
	jmethodID getMesh = env->GetMethodID(hdClass, "getMesh", "()Lde/ibm/dhbw/heimdall/engine/HeimdallMesh;");

		if(getMesh == 0){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, method getMesh not found");
			return;
		}

		jobject meshObj = env->CallObjectMethod(nativeObj, getMesh);

		//Propagate mesh-object

		this->nativeMesh = new HeimdallNativeMesh();
		this->nativeMesh->loadJavaObject(env, meshObj);


	//Get the method-handle to load the HeimdallMesh-object
	jmethodID getSurface = env->GetMethodID(hdClass, "getSurface", "()Lde/ibm/dhbw/heimdall/engine/HeimdallSurface;");

		if(getSurface == 0){
			__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error, method getSurface not found");
			return;
		}

		jobject surfaceObj = env->CallObjectMethod(nativeObj, getSurface);

		//Propagate surface-object
		this->nativeSurface = new HeimdallNativeSurface();
		this->nativeSurface->loadJavaObject(env, surfaceObj);

	//Load the properties for location, rotation and scale

		//loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedPositionFloat", &this->nativePosition);

		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "MSGP: [0]: %f [1]: %f [2]: %f", this->nativePosition[0], this->nativePosition[1], this->nativePosition[2]);
		updatePosition(env, nativeObj, hdClass);

		//loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedRotationFloat", &this->nativeRotation);

		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "MSGR: [0]: %f [1]: %f [2]: %f", this->nativeRotation[0], this->nativeRotation[1], this->nativeRotation[2]);
		updateRotation(env, nativeObj, hdClass);


		//loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedScaleFloat", &this->nativeScale);

		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "MSGS: [0]: %f [1]: %f [2]: %f", this->nativeScale[0], this->nativeScale[1], this->nativeScale[2]);
		updateScale(env, nativeObj, hdClass);

}

/**
 * Execute the remaining setup procedures necessary to make the object drawable.
 * This includes finalizing the vertex-data, color-data etc.
 */
void HeimdallNative3DObject::setupObject(){
	if(!this->isFinalized){
		this->nativeMesh->finalize();
		this->isFinalized = true;
	}
}

/**
 * Load the necessary uniforms into the shader and call the VertexManagementUnit to draw the Vertex-Data.
 * While this application is Alpha, this method will choose the shader itself. In future, the shader will
 * depend on the java-counterpart.
 */
void HeimdallNative3DObject::draw(ShaderManagementUnit* smu, H44DMatrix localModel, H44DMatrix projection){

	H44DMatrix mvp,model2,model,rotation;
	H4DVector color;

	memcpy(model,localModel, sizeof(H44DMatrix));


	//Translate
		translateMatrix(model, this->nativePosition);

	//Rotate
	//H3DVector rot; rot[0] = 0.0f; rot[1] = 0.0f; rot[2] = 90.0f;
		rotateMatrix(model,this->nativeRotation);

	//Scale
		scaleMatrix(model,this->nativeScale);


		H3DMatrixMultiply44(mvp, projection, model);

	switch(this->nativeMesh->getMeshType()){

	case DEFAULT_GRID:
		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Drawing Default Grid");

		this->nativeSurface->getColor(&color);
		smu->PrepareShaders(HD_SHADER_UNIFORM_COLOR, mvp, color);

		break;

	case DEFAULT_BARRIER:
	case DEFAULT_TRAFFICL:
	case DEFAULT_CUBE:
	case DEFAULT_CONE:
	case DEFAULT_FANCYCAR:
		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Drawing Default Grid");

		this->nativeSurface->getColor(&color);
		smu->PrepareShaders(HD_SHADER_POINT_LIGHT, mvp,model,color,lightPosV);

		break;

	case NONE:
		//__android_log_print(ANDROID_LOG_INFO, LOG_TAG, "None stock shader");
		break;

	default:
		__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Well shit. The mesh-type key is invalid");
		break;
	}

	//Draw the vertex-data to the screen using the prepared shader
		this->nativeMesh->draw();
}

void HeimdallNative3DObject::updatePosition(JNIEnv* env, jobject nativeObj,jclass hdClass){

	loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedPositionFloat", &this->nativePosition);
}

void HeimdallNative3DObject::updateScale(JNIEnv* env, jobject nativeObj,jclass hdClass){
	loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedScaleFloat", &this->nativeScale);
}

void HeimdallNative3DObject::updateRotation(JNIEnv* env, jobject nativeObj,jclass hdClass){

	loadFloat3DArray(env, nativeObj, hdClass, "getAdjustedRotationFloat", &this->nativeRotation);
}

int HeimdallNative3DObject::getMarkerID(){
	return this->markerID;
}




